#include "sdat.h"
#include <cstring>
#include <fstream>

namespace nds {

	sdat::sdat() {}
	sdat::~sdat() {}
	
	void sdat::loadSDAT(std::ifstream& is, std::streampos offset) {
	    char compare2[4];
	    int i = 0;
	    int j = 0;
	    int tmpchar = 0;

		if(is.fail()) {
			throw(ndsexcept("loadSDAT: bad input stream"));
		}else {
			is.seekg(offset);
		}
		
		if(is.read((char*)(char*)&this->header, sizeof(NDSSDAT)).fail()) {
			throw(ndsexcept("loadSDAT: could not read file"));
		}else {
			header.ofsSYMB += offset;
			header.ofsINFO += offset;
			header.ofsFAT  += offset;
			header.ofsFILE += offset;
		}

	
		// SYMB Section (may or may not exist)
		is.seekg(header.ofsSYMB);
		if(is.read((char*)compare2, 4).fail()) {
			throw(ndsexcept("loadSDAT: could not read file"));
		}
		if(!memcmp(compare2,"SYMB",4)) {   //Check if there is a symbol block
			SDATSYMB symb;
			SDATSYMBREC symbRec;

			is.seekg(is.tellg()-(std::streampos)4);
			if(is.read((char*)&symb, sizeof(SDATSYMB)).fail()) {
				throw(ndsexcept("loadSDAT: could not read file"));
			}

			{// SSEQ Symbols
				SDATSSEQ seq;
				is.seekg(header.ofsSYMB+symb.nRecOffset[0]);
				if(is.read((char*)&symbRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				symbRec.nEntryOffset = new u32[symbRec.nCount];
				if(is.read((char*)symbRec.nEntryOffset, sizeof(u32)*symbRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				for (i = 0; i < (int)symbRec.nCount; i++) {
					if(!symbRec.nEntryOffset[i]) {
						continue;
					}
					is.seekg(header.ofsSYMB+symbRec.nEntryOffset[i]);
					seq.symbol = new char[256];

					do {
						tmpchar = is.get();
						seq.symbol[j] = tmpchar;
						j++;
					} while(tmpchar);
					j = 0;
					sseq.push_back(seq);
					SAFE_DELETE( seq.symbol );
				}
				SAFE_DELETE( symbRec.nEntryOffset );
			}//\SSEQ Symbols

			{// SBNK Symbols
				SDATSBNK bnk;
				is.seekg(header.ofsSYMB+symb.nRecOffset[2]);
				if(is.read((char*)&symbRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				symbRec.nEntryOffset = new u32[symbRec.nCount];
				if(is.read((char*)symbRec.nEntryOffset, sizeof(u32)*symbRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				for (i = 0; i < (int)symbRec.nCount; i++) {
					if(!symbRec.nEntryOffset[i]) {
						continue;
					}
					is.seekg(header.ofsSYMB+symbRec.nEntryOffset[i]);
					bnk.symbol = new char[256];

					do {
						tmpchar = is.get();
						bnk.symbol[j] = tmpchar;
						j++;
					} while(tmpchar);
					j = 0;
					sbnk.push_back(bnk);
					SAFE_DELETE( bnk.symbol );
				}
				SAFE_DELETE( symbRec.nEntryOffset );
			}//\SBNK Symbols

			{// SWAR Symbols
				SDATSWAR war;
				is.seekg(header.ofsSYMB+symb.nRecOffset[3]);
				if(is.read((char*)&symbRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				symbRec.nEntryOffset = new u32[symbRec.nCount];
				if(is.read((char*)symbRec.nEntryOffset, sizeof(u32)*symbRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				for (i = 0; i < (int)symbRec.nCount; i++) {
					if(!symbRec.nEntryOffset[i]) {
						continue;
					}
					is.seekg(header.ofsSYMB+symbRec.nEntryOffset[i]);
					war.symbol = new char[256];

					do {
						tmpchar = is.get();
						war.symbol[j] = tmpchar;
						j++;
					} while(tmpchar);
					j = 0;
					swar.push_back(war);
					SAFE_DELETE( war.symbol );
 				}
				SAFE_DELETE( symbRec.nEntryOffset );
			}//\SWAR Symbols
	    }//\SYMB Section
	    
		{// INFO Section
			SDATINFO nfo;
			SDATINFOREC nfoRec;
			is.seekg(header.ofsINFO);
			if(is.read((char*)&nfo, sizeof(SDATINFO)).fail()) {
				throw(ndsexcept("loadSDAT: could not read file"));
			}

			{//SSEQ Info
				is.seekg(header.ofsINFO+nfo.nRecOffset[0]);
				if(is.read((char*)&nfoRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				nfoRec.nEntryOffset = new u32[nfoRec.nCount];
				if(is.read((char*)nfoRec.nEntryOffset, sizeof(u32)*nfoRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}

				for (i = 0; i < (int)nfoRec.nCount; i++) {
					is.seekg(header.ofsINFO+nfoRec.nEntryOffset[i]);
					if(is.read((char*)&sseq[i].info, sizeof(SDATINFOSSEQ)).fail()) {
						throw(ndsexcept("loadSDAT: could not read file"));
					}
				}
			}//\SSEQ Info

			{// SBNK Info
				is.seekg(header.ofsINFO+nfo.nRecOffset[2]);
				if(is.read((char*)&nfoRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}
				nfoRec.nEntryOffset = new u32[nfoRec.nCount];
				if(is.read((char*)nfoRec.nEntryOffset, sizeof(u32)*nfoRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}

				for (i = 0; i < (int)nfoRec.nCount; i++) {
					is.seekg(header.ofsINFO+nfoRec.nEntryOffset[i]);
					if(is.read((char*)&sbnk[i].info, sizeof(SDATINFOSBNK)).fail()) {
						throw(ndsexcept("loadSDAT: could not read file"));
					}
				}
			}//\SBNK Info

			{// SWAR Info
				is.seekg(header.ofsINFO+nfo.nRecOffset[3]);
				if(is.read((char*)&nfoRec.nCount, sizeof(u32)).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}

				nfoRec.nEntryOffset = new u32[nfoRec.nCount];
				if(is.read((char*)nfoRec.nEntryOffset, sizeof(u32)*nfoRec.nCount).fail()) {
					throw(ndsexcept("loadSDAT: could not read file"));
				}

				for (i = 0; i < (int)nfoRec.nCount; i++) {
					is.seekg(header.ofsINFO+nfoRec.nEntryOffset[i]);
					if(is.read((char*)&swar[i].info, sizeof(SDATINFOSWAR)).fail()) {
						throw(ndsexcept("loadSDAT: could not read file"));
					}
				}
				delete nfoRec.nEntryOffset;
			}//\SWAR Info
		}//\INFO Section
	    
		{// FAT Section
			SDATFAT fat;
			//SDATFATREC fatRec;
			is.seekg(header.ofsFAT);
			if(is.read((char*)&fat, sizeof(SDATFAT)-4).fail()) {
				throw(ndsexcept("loadSDAT: could not read file"));
			}
			fat.Rec = new SDATFATREC[fat.nCount];
			if(is.read((char*)fat.Rec, sizeof(SDATFATREC)*fat.nCount).fail()) {
				throw(ndsexcept("loadSDAT: could not read file"));
			}
		}//\FAT Section
	}

	void sdat::loadFromRom(std::string romfile) {
	    NDSHeader h;
	    char compare;

	    std::ifstream isFile(romfile.c_str(),std::ios::binary|std::ios::in);
	 	if (isFile.fail()) {
	 		throw(ndsexcept("loadFromRom: could not create ifstream object"));
		}

		if(isFile.read((char*)&h, sizeof(NDSHeader)).fail()) {
			throw(ndsexcept("loadFromRom: could not read file"));
		}isFile.seekg(h.fatOffset);

		do {
			compare = (char)isFile.get();
			if (compare != 'S') {
				continue;
			}

			compare = (char)isFile.get();
			if (compare != 'D') {
				continue;
			}

			compare = (char)isFile.get();
			if (compare != 'A') {
				continue;
			}

			compare = (char)isFile.get();
			if (compare != 'T') {
				continue;
			}

			break;
		} while(!isFile.fail());
		if(isFile.fail()) {
			throw(ndsexcept("loadFromRom: could not find SDAT"));
		}

		offsetInRom = ((int)isFile.tellg())-4;
		loadSDAT(isFile, std::streampos(offsetInRom));
	}

	void sdat::loadFromFile(std::string sdatfile) {	//Load directly from .sdat file
	    std::ifstream isFile(sdatfile.c_str(),std::ios::binary|std::ios::in);
	 	if (isFile.fail()) {
	 		throw(ndsexcept("loadFromFile: could not create ifstream object"));
		}
	 	loadSDAT(isFile, 0);
	}

}; // namespace nds



